/* 
    日志落地模块的实现
        1、 抽象落地类
        2、 派生子类（根据不同的落地方向进行派生）
        3、 使用工厂模式进行创建和表示分离 
*/

#ifndef __CLX_SINK_H__
#define __CLX_SINK_H__

#include "util.hpp"
#include <fstream>
#include <sstream>
#include <memory>
#include <cassert>

namespace clxlog {
    class LogSink {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {}
        virtual void log(const char* data, size_t len) = 0;
    };

    // 落地方向： 标准输出
    class StdoutSink : public LogSink {
        public:
            // 将日志消息写到标准输出
            void log(const char* data, size_t len) {
            // 不要直接使用std::cout << 进行输出，因为不一定是字符串需要按照长度输出
                std::cout.write(data, len);
            }
    };

    // 落地方向： 指定文件
    class FileSink : public LogSink {
        public: 
            // 构造时传入文件名，并打开文件，将操作句柄管理起来
            FileSink(const std::string &pathname) {
                // 1、 创建日志文件所在的目录
                util::File::createDirectory(util::File::path(pathname));
                // 2、 创建并打开日志文件
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }
            void log(const char *data, size_t len) {
                _ofs.write(data, len);
                assert(_ofs.good());
            }
        private:
            std::string _pathname;
            std::ofstream _ofs;
    };

    // 落地方向： 滚动文件（以大小进行滚动）
    class RollBySizeSink : public LogSink {
        public:
            RollBySizeSink(const std::string &basename, size_t max_size) 
                : _basename(basename), _max_fsize(max_size), _cur_fsize(0), _name_count(0)
            {
                std::string pathname = createNewFile();
                util::File::createDirectory(util::File::path(pathname));
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }

            void log(const char *data, size_t len) {
                if (_cur_fsize + len >= _max_fsize) {
                    _ofs.close();                         // 关闭原来已经打开的文件
                    std::string pathname = createNewFile();
                    _cur_fsize = 0;
                    util::File::createDirectory(util::File::path(pathname));
                    _ofs.open(pathname, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());
                }
                _ofs.write(data, len);
                _cur_fsize += len;
                assert(_ofs.good());
            }
        private:
            std::string createNewFile() {
                _name_count += 1;
                // 获取系统时间，以时间来构建文件扩展名
                time_t t = util::Data::now();
                struct tm lt;
                localtime_r(&t, &lt);
                std::stringstream filename;
                filename << _basename;
                filename << (lt.tm_year + 1900);
                filename << "-";
                filename << (lt.tm_mon + 1);
                filename << "-";
                filename << lt.tm_mday;
                filename << " ";
                filename << lt.tm_hour;
                filename << ":";
                filename << lt.tm_min;
                filename << ":";
                filename << lt.tm_sec;
                filename << "-";
                filename << _name_count;
                filename << ".log";
                return filename.str();
            }
        private:
            std::string _basename;   // 基础文件名 + 扩展文件名(以时间来生成) = 实际输出文件名/   
            std::ofstream _ofs;      // 操作句柄        
            size_t _max_fsize;       // 记录文件允许存储最大数据量
            size_t _cur_fsize;       // 记录当前文件已经写入数据大小
            size_t _name_count;
    };

    class SinkFactory {
        public:
            template<typename SinkType, typename ...Args>
            static LogSink::ptr create(Args&&... args) {
                return std::make_shared<SinkType>(std::forward<Args>(args)...);
            }
    };
}

#endif